package com.carey.blog.jsonrpc.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.JSONObject;

import com.carey.blog.action.StatusCodes;
import com.carey.blog.event.EventTypes;
import com.carey.blog.framework.latke.Keys;
import com.carey.blog.framework.latke.action.ActionException;
import com.carey.blog.framework.latke.action.util.PageCaches;
import com.carey.blog.framework.latke.event.Event;
import com.carey.blog.framework.latke.event.EventManager;
import com.carey.blog.framework.latke.repository.Transaction;
import com.carey.blog.jsonrpc.AbstractGAEJSONRpcService;
import com.carey.blog.listener.CareyBlogServletListener;
import com.carey.blog.model.Article;
import com.carey.blog.model.Comment;
import com.carey.blog.model.Page;
import com.carey.blog.repository.ArticleCommentRepository;
import com.carey.blog.repository.CommentRepository;
import com.carey.blog.repository.PageCommentRepository;
import com.carey.blog.repository.PageRepository;
import com.carey.blog.repository.impl.ArticleCommentGAERepository;
import com.carey.blog.repository.impl.CommentGAERepository;
import com.carey.blog.repository.impl.PageCommentGAERepository;
import com.carey.blog.repository.impl.PageGAERepository;
import com.carey.blog.util.Articles;
import com.carey.blog.util.Pages;
import com.carey.blog.util.Preferences;
import com.carey.blog.util.Statistics;
import com.carey.blog.util.Users;

/**
 * Comment service for JavaScript client.
 * 
 * <p>
 * Comment adding operation via servlet
 * {@link com.carey.blog.action.impl.AddArticleCommentAction} and
 * {@link com.carey.blog.action.impl.AddPageCommentAction}.
 * </p>
 * 
 */
public final class CommentService extends AbstractGAEJSONRpcService {

	/**
	 * Logger.
	 */
	private static final Logger LOGGER = Logger.getLogger(CommentService.class
			.getName());
	/**
	 * Comment repository.
	 */
	private CommentRepository commentRepository = CommentGAERepository
			.getInstance();
	/**
	 * Event manager.
	 */
	private EventManager eventManager = EventManager.getInstance();
	/**
	 * Article-Comment repository.
	 */
	private ArticleCommentRepository articleCommentRepository = ArticleCommentGAERepository
			.getInstance();
	/**
	 * Page-Comment repository.
	 */
	private PageCommentRepository pageCommentRepository = PageCommentGAERepository
			.getInstance();
	/**
	 * Page repository.
	 */
	private PageRepository pageRepository = PageGAERepository.getInstance();
	/**
	 * Article utilities.
	 */
	private Articles articleUtils = Articles.getInstance();
	/**
	 * Page utilities.
	 */
	private Pages pageUtils = Pages.getInstance();
	/**
	 * Statistic utilities.
	 */
	private Statistics statistics = Statistics.getInstance();
	/**
	 * User utilities.
	 */
	private Users userUtils = Users.getInstance();
	/**
	 * Preference utilities.
	 */
	private Preferences preferenceUtils = Preferences.getInstance();

	// /**
	// * Gets recent comments with the specified http servlet request and
	// response.
	// *
	// * @param request the specified http servlet request
	// * @param response the specified http servlet response
	// * @return for example:
	// * <pre>
	// * {
	// * "recentComments": [{
	// * "oId": "",
	// * "commentName": "",
	// * "thumbnailUrl": "",
	// * "commentURL": "",
	// * "commentContent": "",
	// * "commentDate": "",
	// * }, ....]
	// * "sc": "GET_COMMENTS_SUCC"
	// * }
	// * </pre>
	// * @throws ActionException action exception
	// * @throws IOException io exception
	// */
	// public JSONObject getRecentComments(final HttpServletRequest request,
	// final HttpServletResponse response)
	// throws ActionException, IOException {
	// final JSONObject ret = new JSONObject();
	// try {
	// final JSONObject preference = preferenceUtils.getPreference();
	// if (null == preference) {
	// response.sendError(HttpServletResponse.SC_NOT_FOUND);
	// return ret;
	// }
	//
	// final int fetchSize = preference.getInt(
	// Preference.RECENT_COMMENT_DISPLAY_CNT);
	// final List<JSONObject> recentComments =
	// commentRepository.getRecentComments(fetchSize);
	// // Erase email for security reason
	// for (final JSONObject comment : recentComments) {
	// comment.remove(Comment.COMMENT_EMAIL);
	// }
	//
	// ret.put(Common.RECENT_COMMENTS, recentComments);
	// ret.put(Keys.STATUS_CODE, StatusCodes.GET_COMMENTS_SUCC);
	// } catch (final Exception e) {
	// LOGGER.log(Level.SEVERE, e.getMessage(), e);
	// throw new ActionException(e);
	// }
	//
	// return ret;
	// }

	/**
	 * Gets comments of an article specified by the article id for
	 * administrator.
	 * 
	 * @param requestJSONObject
	 *            the specified request json object, for example,
	 * 
	 *            <pre>
	 * {
	 *     "oId": articleId
	 * }
	 * </pre>
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "comments": [{
	 *         "oId": "",
	 *         "commentName": "",
	 *         "commentEmail": "",
	 *         "thumbnailUrl": "",
	 *         "commentURL": "",
	 *         "commentContent": "",
	 *         "commentDate": "",
	 *         "commentSharpURL": ""
	 *      }, ....]
	 *     "sc": "GET_COMMENTS_SUCC"
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 */
	public JSONObject getCommentsOfArticle(final JSONObject requestJSONObject,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException, IOException {
		final JSONObject ret = new JSONObject();
		if (!userUtils.isLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return ret;
		}

		try {
			final String articleId = requestJSONObject
					.getString(Keys.OBJECT_ID);
			// Step 1: Get article-comment relations
			final List<JSONObject> articleCommentRelations = articleCommentRepository
					.getByArticleId(articleId);
			// Step 2: Get comments
			final List<JSONObject> comments = new ArrayList<JSONObject>();
			for (int i = 0; i < articleCommentRelations.size(); i++) {
				final JSONObject articleCommentRelation = articleCommentRelations
						.get(i);
				final String commentId = articleCommentRelation
						.getString(Comment.COMMENT + "_" + Keys.OBJECT_ID);

				final JSONObject comment = commentRepository.get(commentId);
				final String content = comment.getString(
						Comment.COMMENT_CONTENT).replaceAll(
						CareyBlogServletListener.ENTER_ESC, "<br/>");
				comment.put(Comment.COMMENT_CONTENT, content);
				comments.add(comment);
			}

			ret.put(Comment.COMMENTS, comments);
			ret.put(Keys.STATUS_CODE, StatusCodes.GET_COMMENTS_SUCC);
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		return ret;
	}

	/**
	 * Gets comments of a page specified by the page id for administrator.
	 * 
	 * @param requestJSONObject
	 *            the specified request json object, for example,
	 * 
	 *            <pre>
	 * {
	 *     "oId": pageId
	 * }
	 * </pre>
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "comments": [{
	 *         "oId": "",
	 *         "commentName": "",
	 *         "commentEmail": "",
	 *         "thumbnailUrl": "",
	 *         "commentURL": "",
	 *         "commentContent": "",
	 *         "commentDate": "",
	 *         "commentSharpURL": ""
	 *      }, ....]
	 *     "sc": "GET_COMMENTS_SUCC"
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 */
	public JSONObject getCommentsOfPage(final JSONObject requestJSONObject,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException, IOException {
		final JSONObject ret = new JSONObject();
		if (!userUtils.isLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return ret;
		}

		try {
			final String pageId = requestJSONObject.getString(Keys.OBJECT_ID);
			// Step 1: Get page-comment relations
			final List<JSONObject> pageCommentRelations = pageCommentRepository
					.getByPageId(pageId);
			// Step 2: Get comments
			final List<JSONObject> comments = new ArrayList<JSONObject>();
			for (int i = 0; i < pageCommentRelations.size(); i++) {
				final JSONObject pageCommentRelation = pageCommentRelations
						.get(i);
				final String commentId = pageCommentRelation
						.getString(Comment.COMMENT + "_" + Keys.OBJECT_ID);

				final JSONObject comment = commentRepository.get(commentId);
				comments.add(comment);
			}

			ret.put(Comment.COMMENTS, comments);
			ret.put(Keys.STATUS_CODE, StatusCodes.GET_COMMENTS_SUCC);
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		return ret;
	}

	/**
	 * Removes a comment of an article by the specified request json object.
	 * 
	 * @param requestJSONObject
	 *            the specified request json object, for example,
	 * 
	 *            <pre>
	 * {
	 *     "oId": commentId,
	 * }
	 * </pre>
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "sc": "REMOVE_COMMENT_SUCC"
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 */
	public JSONObject removeCommentOfArticle(
			final JSONObject requestJSONObject,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException, IOException {
		final JSONObject ret = new JSONObject();
		if (!userUtils.isLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return ret;
		}

		final Transaction transaction = commentRepository.beginTransaction();
		try {
			final String commentId = requestJSONObject
					.getString(Keys.OBJECT_ID);
			LOGGER.log(Level.FINER, "Removing comment[oId={0}]", commentId);

			// Step 1: Remove article-comment relation
			final JSONObject articleCommentRelation = articleCommentRepository
					.getByCommentId(commentId);
			final String articleCommentRelationId = articleCommentRelation
					.getString(Keys.OBJECT_ID);
			articleCommentRepository.remove(articleCommentRelationId);

			final String articleId = articleCommentRelation
					.getString(Article.ARTICLE + "_" + Keys.OBJECT_ID);

			if (!userUtils.canAccessArticle(articleId)) {
				ret.put(Keys.STATUS_CODE,
						StatusCodes.REMOVE_COMMENT_FAIL_FORBIDDEN);

				return ret;
			}

			// Step 2: Remove comment
			commentRepository.remove(commentId);
			// Step 3: Update article comment count
			articleUtils.decArticleCommentCount(articleId);
			// Step 4: Update blog statistic comment count
			statistics.decBlogCommentCount();
			statistics.decPublishedBlogCommentCount();
			// Step 5: Fire remove comment event
			eventManager.fireEventSynchronously(new Event<String>(
					EventTypes.REMOVE_COMMENT, articleId));

			PageCaches.removeAll();

			transaction.commit();
			ret.put(Keys.STATUS_CODE, StatusCodes.REMOVE_COMMENT_SUCC);

			LOGGER.log(Level.FINER, "Removed comment[oId={0}]", commentId);
		} catch (final Exception e) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		return ret;
	}

	/**
	 * Removes a comment of a page by the specified request json object.
	 * 
	 * @param requestJSONObject
	 *            the specified request json object, for example,
	 * 
	 *            <pre>
	 * {
	 *     "oId": commentId,
	 * }
	 * </pre>
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "sc": "REMOVE_COMMENT_SUCC"
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 */
	public JSONObject removeCommentOfPage(final JSONObject requestJSONObject,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException, IOException {
		final JSONObject ret = new JSONObject();
		if (!userUtils.isAdminLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return ret;
		}

		final Transaction transaction = commentRepository.beginTransaction();
		try {
			final String commentId = requestJSONObject
					.getString(Keys.OBJECT_ID);
			LOGGER.log(Level.FINER, "Removing comment[oId={0}]", commentId);

			// Step 1: Remove page-comment relation
			final JSONObject pageCommentRelation = pageCommentRepository
					.getByCommentId(commentId);
			final String pageCommentRelationId = pageCommentRelation
					.getString(Keys.OBJECT_ID);
			pageCommentRepository.remove(pageCommentRelationId);

			final String pageId = pageCommentRelation.getString(Page.PAGE + "_"
					+ Keys.OBJECT_ID);
			// Step 2: Remove comment
			commentRepository.remove(commentId);
			// Step 3: Update page comment count
			pageUtils.decPageCommentCount(pageId);
			// Step 4: Update blog statistic comment count
			statistics.decBlogCommentCount();
			statistics.decPublishedBlogCommentCount();
			// Step 5: Fire remove comment event
			eventManager.fireEventSynchronously(new Event<String>(
					EventTypes.REMOVE_COMMENT, pageId));

			PageCaches.removeAll();

			transaction.commit();
			ret.put(Keys.STATUS_CODE, StatusCodes.REMOVE_COMMENT_SUCC);

			LOGGER.log(Level.FINER,
					"Removed comment[oId={0}] of page[{oId={1}}]",
					new String[] { commentId, pageId });
		} catch (final Exception e) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		return ret;
	}

	/**
	 * Gets the {@link CommentService} singleton.
	 * 
	 * @return the singleton
	 */
	public static CommentService getInstance() {
		return SingletonHolder.SINGLETON;
	}

	/**
	 * Private default constructor.
	 */
	private CommentService() {
	}

	/**
	 * Singleton holder.
	 * 
	 */
	private static final class SingletonHolder {

		/**
		 * Singleton.
		 */
		private static final CommentService SINGLETON = new CommentService();

		/**
		 * Private default constructor.
		 */
		private SingletonHolder() {
		}
	}
}
